package com.enba.boot.security.config;

import com.enba.boot.security.filter.CaptchaFilter;
import com.enba.boot.security.filter.TokenFilter;
import com.enba.boot.security.properties.SecurityCaptchaProperties;
import com.enba.boot.security.properties.SecurityIgnoreUrlsProperties;
import com.enba.boot.security.properties.SecurityUrlProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

  private final CaptchaFilter captchaFilter;
  private final TokenFilter tokenFilter;
  private final PasswordEncoder passwordEncoder;
  private final UserDetailsService userDetailsService;
  private final SecurityIgnoreUrlsProperties ignoreUrlsProperties;
  private final SecurityCaptchaProperties captchaProperties;
  private final SecurityUrlProperties securityUrlProperties;
  private final AuthenticationSuccessHandler jwtAuthenticationSuccessHandler;
  private final LogoutSuccessHandler jwtLogoutSuccessHandler;
  private final AuthenticationFailureHandler jwtAuthenticationFailureHandler;
  private final AuthenticationEntryPoint jwtAuthenticationEntryPoint;
  private final AccessDeniedHandler jwtAccessDeniedHandler;

  public WebSecurityConfiguration(
      CaptchaFilter captchaFilter,
      TokenFilter tokenFilter,
      PasswordEncoder passwordEncoder,
      UserDetailsService userDetailsService,
      SecurityIgnoreUrlsProperties ignoreUrlsProperties,
      SecurityCaptchaProperties captchaProperties,
      SecurityUrlProperties securityUrlProperties,
      AuthenticationSuccessHandler jwtAuthenticationSuccessHandler,
      LogoutSuccessHandler jwtLogoutSuccessHandler,
      AuthenticationFailureHandler jwtAuthenticationFailureHandler,
      AuthenticationEntryPoint jwtAuthenticationEntryPoint,
      AccessDeniedHandler jwtAccessDeniedHandler) {
    this.captchaFilter = captchaFilter;
    this.tokenFilter = tokenFilter;
    this.passwordEncoder = passwordEncoder;
    this.userDetailsService = userDetailsService;
    this.ignoreUrlsProperties = ignoreUrlsProperties;
    this.captchaProperties = captchaProperties;
    this.securityUrlProperties = securityUrlProperties;
    this.jwtLogoutSuccessHandler = jwtLogoutSuccessHandler;
    this.jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler;
    this.jwtAuthenticationFailureHandler = jwtAuthenticationFailureHandler;
    this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
    this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;

    // 登陆接口添加到白名单
    //    ignoreUrlsProperties.getUrls().add(securityUrlProperties.getLoginUrl());
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // 白名单
    for (String url : ignoreUrlsProperties.getUrls()) {
      http.authorizeRequests().antMatchers(url).permitAll();
    }

    http.csrf()
        // 关闭csrf
        .disable()

        // 关闭session
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()

        // 开启认证请求
        .authorizeRequests()
        .antMatchers(HttpMethod.OPTIONS, "/**")
        .permitAll()

        // 其他全部需要认证
        .anyRequest()
        .authenticated()
        .and()

        // 登录
        .formLogin()
        .usernameParameter(securityUrlProperties.getUsernameParameter())
        .passwordParameter(securityUrlProperties.getPasswordParameter())
        .loginProcessingUrl(securityUrlProperties.getLoginUrl())
        .successHandler(jwtAuthenticationSuccessHandler)
        .failureHandler(jwtAuthenticationFailureHandler)
        .permitAll()
        .and()

        // 注销
        .logout()
        .logoutUrl("/logout")
        .logoutSuccessHandler(jwtLogoutSuccessHandler)
        .and()
        .exceptionHandling()

        // 认证失败
        .authenticationEntryPoint(jwtAuthenticationEntryPoint)
        .accessDeniedHandler(jwtAccessDeniedHandler);

    // 验证码过滤器
    if (captchaProperties.isEnabled()) {
      http.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);
    }

    // token校验过滤器
    http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
    http.headers().cacheControl();
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(daoAuthenticationProvider());
  }

  @Bean
  public DaoAuthenticationProvider daoAuthenticationProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    // 如果设置了provider.setHideUserNotFoundExceptions(false)，那么当找不到用户时，
    // 将会抛出UsernameNotFoundException，而不是隐藏这个错误并抛出更通用的BadCredentialsException
    provider.setHideUserNotFoundExceptions(false);
    provider.setUserDetailsService(userDetailsService);
    provider.setPasswordEncoder(passwordEncoder);
    return provider;
  }
}
